<?php namespace Remix;

/**
 * 请求分发类 用于处理route的注册 以及分发到对应的方法
 */
class Dispatcher
{
    /**
     * 存储已经注册的路由
     */
    public static $registered_routes = array(
        'GET'    => array(),
        'POST'   => array(),
        'PUT'    => array(),
        'DELETE' => array(),
        'PATCH'  => array(),
        'HEAD'   => array(),
        'OPTIONS'=> array(),
    );


    /**
     * All of the "fallback" routes that have been registered.
     *
     * @var array
     */
    public static $fallback = array(
        'GET'    => array(),
        'POST'   => array(),
        'PUT'    => array(),
        'DELETE' => array(),
        'PATCH'  => array(),
        'HEAD'   => array(),
        'OPTIONS'=> array(),
    );


    /**
     * 路由正则
     *
     * @var array
     */
    public static $patterns = array(
        '(:num)' => '([0-9]+)',
        '(:any)' => '([a-zA-Z0-9\.\-_%=]+)',
        '(:segment)' => '([^/]+)',
        '(:all)' => '(.*)',
    );

    /**
     * 可选路由的正则
     *
     * @var array
     */
    public static $optional = array(
        '/(:num?)' => '(?:/([0-9]+)',
        '/(:any?)' => '(?:/([a-zA-Z0-9\.\-_%=]+)',
        '/(:segment?)' => '(?:/([^/]+)',
        '/(:all?)' => '(?:/(.*)',
    );

    /**
     * 一些常见的请求方法
     */
    public static $methods = array('GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS');

    /**
     * 这里存储的是route中注册的方法 只是注册 并未调用!
     *
     */
    public static function register($method, $route, $action) {

        /**
         * 这里支持$method为array 意味着可以注册多个响应多个路由
         * Dispather::register(array('GET', 'POST'), $url, $action)
         * Dispather::register(array('GET', 'POST'), array('/', 'about'), 'demoController');
         * Dispather::register(array('GET', 'POST'), '*', 'demoController');
         * 所以当method是数组的时候 我们采用遍历的形式 循环注册
         */
        if (is_array($method)) {
            foreach ($method as $http_verb) {
                static::register($http_verb, $route, $action);
                return;
            }
        }

        foreach((array)$route as $uri) {
            /**
             * 通配符处理 *
             * 注册所有的方法
             */
            if ('*' == $method) {
                foreach (static::$methods as $http_verb) {
                    static::register($http_verb, $route, $action);
                }

                continue;
            }

            //去除最左边的/
            $uri = ltrim($uri, '/');
            if ('' == $uri) {
                $uri = '/';
            }

            //判断是否有正则规则
            if ('(' == $uri[0]) {
                $routes =& static::$fallback;
            } else {
                $routes =& static::$registered_routes;
            }

            //判断action
            //如果是数组 我们用uri作为key来存储
            //否则 就是一个action 则去调用action方法转成数组的形式作为结果
            //这里注意同一个uri的顺序 后注册的路由会把前面的覆盖掉
            if (is_array($action)) {
                $routes[$method][$uri] = $action;
            } else {
                $routes[$method][$uri] = static::action($action);
            }
        }
    }

    /**
     * 把字符串形式或者闭包形式的action转换为数组
     */
    public static function action($action) {
        //把单独的controller调用转换为数组中的uses
        if (is_string($action))
        {
            $action = array('uses' => $action);
        }
        elseif ($action instanceof Closure)
        {
            $action = array($action);
        }

        return (array) $action;
    }

    /**
     * 根据传入的httpverb和uri解析路由 返回一个route实例对象
     */
    public static function parse($method, $uri) {

        $routes = (array) static::method($method);//取得对应method的route数组

        //检测注册的路由中有没有符合当前url的 如果有说明之前注册过 直接返回route对象
        if (array_key_exists($uri, $routes))
        {
            $action = $routes[$uri];
            return new Route($method, $uri, $action);
        }

        // 如果找不到直接匹配的 则根据正则表达式或者通配符去寻找对应的路由
        if ( ! is_null($route = static::match($method, $uri)))
        {
            return $route;
        }

    }

    /**
     * 根据传入的method 获取所有的路由信息
     *
     * @param  string  $method
     * @return array
     */
    public static function method($method)
    {
        $routes = array_get(static::$registered_routes, $method, array());

        return array_merge($routes, array_get(static::$fallback, $method, array()));
    }


    /**
     * 遍历路由表寻找匹配的路由
     *
     * @param  string  $method
     * @param  string  $uri
     * @return Route
     */
    protected static function match($method, $uri)
    {
        foreach (static::method($method) as $route => $action)
        {
            //这里我们只需要检查含有(的即可
            if (str_contains($route, '('))
            {
                //转换成对应的正则规则
                $pattern = '#^'.static::wildcards($route).'$#u';

                if (preg_match($pattern, $uri, $parameters))
                {
                    return new Route($method, $route, $action, array_slice($parameters, 1));
                }
            }
        }
    }

    /**
     * 转换路由通配符成正则
     * @param  string  $key
     * @return string
     */
    protected static function wildcards($key)
    {
        list($search, $replace) = array_divide(static::$optional);

        // 对于可选参数,首先把通配符转成正则 然后根据匹配次数加上)?
        $key = str_replace($search, $replace, $key, $count);

        if ($count > 0)
        {
            $key .= str_repeat(')?', $count);
        }

        return strtr($key, static::$patterns);
    }

}